
import httpx
import os,logging
from pyquery import PyQuery as pq
from abc import ABCMeta, abstractmethod

from pyquery.pyquery import PyQuery

from core.log import get_logger

from .item import *
from .exception_base import *
from .request_base import Request
from .utiltiy import *
from .item_manager import *


logging.basicConfig(level=logging.DEBUG,format="%(asctime)s [%(name)s][%(levelname)s] => %(message)s")
logger=logging.getLogger('Spider')


class BookSpider(metaclass=ABCMeta):
    logger=get_logger('Spider')

    def __init__(self,request=None,logger=None,*args,**kwargs) -> None:        
        super().__init__(*args,**kwargs)
        self.request=request if request else Request()
        self.logger=logger if logger else get_logger('Spider')
        self.progress_changed_handlers=[]
        self.base_url=None
        self.search_url=None

    
    def _progress_changed(self,progress:float):
        if not self.progress_changed_handlers:
            return
        for handler in self.progress_changed_handlers:
            handler(progress)

    @abstractmethod
    def parse_book(self,book_selector:PyQuery) -> Tuple[str,str,list[str]]:
        """
        Return (book_title,book_cover_url,chapter_url_list)
        """
        pass
    
    @abstractmethod
    def parse_chapter(self,chapter_selector:PyQuery)->Tuple[str,list[str]]:
        """
        Return (chapter_title,image_url_list)
        """
        pass
    
    @abstractmethod
    def parse_search(self,selector:PyQuery,result_count)->list[str]:
        """
        Return list of book urls
        """
        pass

    def _get_content(self,url):
        res=self.request.get(url)
        return res.content
    
    def _get_selector(self,url):
        res= self.request.get(url)
        return pq(res.text)
    
    def join_url(self,url):
        """
        将路径加上网站的基路径
        """
        if not self.base_url:
            raise Exception("Please set spider's base url first!")
        return self.base_url+url      
    
    def search(self,keyword,limit=10)->list[BookItem]:
        BookItem.delete().where(BookItem.state==ItemState.Temp).execute()

        if not self.search_url:
            raise Exception('Please set search api first!')
        book_url_list=self.parse_search(self._get_selector(self.search_url+keyword),limit)
        book_items=[]
   
        for book_url in book_url_list:
            book_item=BookItem.get_or_none(BookItem.url==book_url)
            if not book_item:                
                book_title,cover_url,chapter_url_list=self.parse_book(self._get_selector(book_url))
                chapter_count=len(chapter_url_list)
                book_item= ItemManager.add_book_item(book_title,book_url,cover_url,chapter_count,ItemState.Temp)
            book_items.append(book_item)
        return book_items

    def _add_new_chapter(self,chapter_url,order,book_item):
        try:
            self.logger.debug('Start get chapter info...')
            chapter_title,page_url_list= self.parse_chapter(self._get_selector(chapter_url))
            chapter_item=ItemManager.add_chapter_item(chapter_title,order,chapter_url,len(page_url_list),book_item)
            ItemManager.add_page_items(page_url_list,chapter_item)
            ItemManager.update_item_state(chapter_item,ItemState.Collected)
            return chapter_item
        except ExceedRetryTimeException:
            self.logger.error(f'Request for chapter from {chapter_url} failed!')

    def start_parse(self,start_url):
        self.book_url=start_url

        self.logger.debug('Start get book info...')
        book_item=BookItem.get_or_none(BookItem.url==self.book_url)
        if book_item:
            self.logger.debug(f'Book[{book_item.title}] has already existed, maybe to update book info...')
            return

        try:
            book_selector=self._get_selector(self.book_url)
        except ExceedRetryTimeException:
            self.logger.error('Request for book info was rejected by server!')
            return
        
        book_title,chatper_url_list=self.parse_book(book_selector)
        chapter_count=len(chatper_url_list)
        book_item=ItemManager.add_book_item(book_title,self.book_url,chapter_count)

        for index,chapter_url in enumerate(chatper_url_list):
            self._add_new_chapter(chapter_url,index,book_item)
            self._progress_changed((index+1)/chapter_count)
        ItemManager.update_item_state(book_item,ItemState.Collected)
        return book_item
        

    def update_book(self,book_item:BookItem):
        self.logger.debug(f'Start to update Book[{book_item.title}]...')
        # ItemManager.update_item_state(book_item,ItemState.Collecting)
        try:
            _,_,chapter_url_list=self.parse_book(self._get_selector(book_item.url))
        except ExceedRetryTimeException:
            self.logger.error('Request for book info denied by server!')

        if len(chapter_url_list) == book_item.chapter_count and book_item.state>=ItemState.Collected:
            self.logger.debug(f'Book[{book_item.title}] has not been updated.')
            return
        
        ItemManager.update_item_state(book_item,ItemState.Collecting)
        ItemManager.update(book_item,'chapter_count',len(chapter_url_list))

        for index,chapter_url in enumerate(chapter_url_list):
            chapter_item=ChapterItem.get_or_none(ChapterItem.url==chapter_url)
            if not chapter_item:
                chapter_item= self._add_new_chapter(chapter_url,index,book_item)
           
            self._update_chapter(chapter_item)
            # self._progress_changed((index+1)/book_item.chapter_count)
        ItemManager.update_item_state(book_item,ItemState.Collected)
        return book_item

    def _update_chapter(self,chapter_item:ChapterItem):
        if chapter_item.state>=ItemState.Collected:
            self.logger.debug(f'Chapter[{chapter_item.title}] do not need to updat.')
            return
        _,page_url_list= self.parse_chapter(self._get_selector(chapter_item.url))
        for index,page_url in enumerate(page_url_list):
            if PageItem.get_or_none(PageItem.url==page_url):
                continue
            ItemManager.add_page_item(index,page_url,chapter_item)
        ItemManager.update_item_state(chapter_item,ItemState.Collected)
            
